package org.hawk.gwt.ppc.scanner;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.hawk.gwt.ppc.utils.Name;

/**
 * Loads java sources from the jar file.
 * @author alex.bereznevatiy@gmail.com
 *
 */
public class JarSourcesLoader extends AbstractSourcesLoader 
		implements SourcesLoader {
	private static final String JAR_PROTOCOL = "jar:";
	private static final char QUERY_SEPARATOR = '!';
	private static final char JAR_ENTRY_SEPARATOR = '/';
	private JarFile jarFile;
	private Map<String,List<JarEntry>> packages;
	/**
	 * Creates {@link JarSourcesLoader} for the location of
	 * the jar file.
	 * @param jarLocation
	 * @throws IOException
	 */
	public JarSourcesLoader(String jarLocation) throws IOException{
		this(new JarFile(jarLocation));
	}
	/**
	 * Creates {@link JarSourcesLoader} for the location of
	 * the jar file.
	 * @param jarLocation
	 * @throws IOException
	 */
	public JarSourcesLoader(File jarLocation) throws IOException {
		this(new JarFile(jarLocation));
	}
	/**
	 * Creates {@link JarSourcesLoader} for the jar file specified.
	 * @param jarFile
	 */
	public JarSourcesLoader(JarFile jarFile) {
		if(jarFile==null){
			throw new NullPointerException();
		}
		this.jarFile = jarFile;
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.SourcesLoader#getClasses(java.lang.String)
	 */
	public List<String> getClasses(String packageName) {
		validatePackageName(packageName);
		readJarIfNecessary();
		List<JarEntry> entries = packages.get(packageName);
		if(entries==null){
			return Collections.emptyList();
		}
		ArrayList<String> result = new ArrayList<String>();
		for(JarEntry entry : entries){
			addIfJavaSrc(result,entry,packageName);
		}
		Collections.sort(result);
		result.trimToSize();
		return Collections.unmodifiableList(result);
	}
	
	private void addIfJavaSrc(ArrayList<String> result,JarEntry entry, String packageName) {
		String name = getFileName(entry);
		if(!Name.isJavaSrc(name)){
			return;
		}
		result.add(Name.getChildJavaName(
			packageName,Name.fsToJava(name)));
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.SourcesLoader#getResource(java.lang.String)
	 */
	public URL getResource(String name) {
		if(jarFile.getEntry(name)==null){
			return null;
		}
		try{
			StringBuilder uri = new StringBuilder();
			uri.append(JAR_PROTOCOL);
			uri.append(new File(jarFile.getName()).toURI());
			uri.append(QUERY_SEPARATOR);
			uri.append(JAR_ENTRY_SEPARATOR);
			uri.append(name);
			URI url = new URI(uri.toString());
			url.normalize();
			return url.toURL();
		}
		catch(Exception ex){
			return null;
		}
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.SourcesLoader#getSubpackages(java.lang.String)
	 */
	public List<String> getSubpackages(String packageName) {
		validatePackageName(packageName);
		ArrayList<String> result = new ArrayList<String>();
		for(String pkg : packages.keySet()){
			if(Name.isDirectSubPackage(pkg,packageName)){
				result.add(pkg);
			}
		}
		Collections.sort(result);
		result.trimToSize();
		return Collections.unmodifiableList(result);
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.SourcesLoader#toUrl()
	 */
	public URL toUrl() {
		try {
			return new File(jarFile.getName()).toURI().toURL();
		}
		catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}
	private void readJarIfNecessary() {
		if(packages!=null)return;
		packages = new HashMap<String, List<JarEntry>>();
		Enumeration<JarEntry> enties = jarFile.entries();
		while(enties.hasMoreElements()){
			JarEntry entry = enties.nextElement();
			String pkg = getPackageName(entry);
			if(packages.get(pkg)==null){
				String tmp = pkg;
				do {
					packages.put(tmp, new LinkedList<JarEntry>());
					tmp = Name.getParentJavaName(pkg);
				}
				while(tmp.length()>0&&
					packages.get(tmp)==null);
			}
			packages.get(pkg).add(entry);
		}
	}
	
	private String getPackageName(JarEntry entry) {
		int i = entry.getName().lastIndexOf(JAR_ENTRY_SEPARATOR);
		if(i<0){
			return "";
		}
		return entry.getName().substring(0,i)
			.replace(JAR_ENTRY_SEPARATOR, Name.PACKAGE_SEPARATOR);
	}
	
	private String getFileName(JarEntry entry) {
		int i = entry.getName().lastIndexOf(JAR_ENTRY_SEPARATOR);
		if(i<0){
			return entry.getName();
		}
		return entry.getName().substring(i+1);
	}
	@Override
	public String toString() {
		return "Jar[" + jarFile + "]";
	}
	@Override
	public boolean equals(Object obj) {
		if(this==obj)return true;
		if(obj instanceof JarSourcesLoader){
			JarSourcesLoader loader = (JarSourcesLoader)obj;
			return loader.jarFile.getName()
				.equals(jarFile.getName());
		}
		return false;
	}
}
